package org.zetaframework.controller.extra;

import cn.hutool.core.io.FileMagicNumber;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.FastExcel;
import cn.idev.excel.read.builder.ExcelReaderBuilder;
import cn.idev.excel.read.listener.ReadListener;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.handler.WriteHandler;
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import org.zetaframework.annotation.log.SysLog;
import org.zetaframework.annotation.satoken.PreCheckPermission;
import org.zetaframework.annotation.satoken.PreMode;
import org.zetaframework.base.excel.handler.ExcelStyleHandler;
import org.zetaframework.base.excel.handler.FreezePaneHandler;
import org.zetaframework.base.param.ImportExcelTemplateParam;
import org.zetaframework.base.result.ApiResult;
import org.zetaframework.controller.SuperBaseController;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Excel导入Controller
 *
 * @param <ImportBean> Excel导入实体
 *
 * @author gcc
 */
public interface ImportController<ImportBean> extends SuperBaseController {

    /**
     * 获取导入实体的类型
     */
    default Class<ImportBean> getImportExcelClass() {
        Optional<Type> first = Arrays.stream(this.getClass().getGenericInterfaces())
                // 筛选出类上面的ImportController接口
                .filter(it -> Objects.equals(((ParameterizedType) it).getRawType().getTypeName(), ImportController.class.getTypeName()))
                .findFirst();
        if (first.isEmpty()) { return null; }

        Type type = first.get();
        // 获取当前接口的第一个泛型的值(下标从0开始)。 即ImportBean的值
        Type argument = ((ParameterizedType) type).getActualTypeArguments()[0];
        return (Class<ImportBean>) argument;
    }

    /**
     * 获取导入模板
     * <p>
     * 说明：<br>
     * 1.默认导出ImportBean对象生成的Excel模板
     * 2.如果想要导出resource目录下的Excel模板，请重写该方法
     *
     * @param param  获取导入Excel模板 参数
     * @param response 响应
     */
    @PreCheckPermission(value = {"{}:import", "{}:view"}, mode = PreMode.OR)
    @Operation(summary = "获取Excel导入模板", description = """
    <pre>
    获取导入模板接口传参示例：
    GET /demo/import/template?fileName=用户列表&sheetName=&freezeHeader=&timestampSuffix=
    
    参数说明：
    {
        fileName: "用户列表",   // 【必传】excel模板文件名,不带扩展名.xlsx
        sheetName: null,       // 【非必传】sheet名称,不传默认为：Sheet1
        freezeHeader: null,    // 【非必传】是否冻结表头，不传默认为：true
        timestampSuffix: null, // 【非必传】文件名是否添加时间戳后缀，不传默认为：true
    }
    </pre>
    """)
    @SysLog(response = false)
    @GetMapping(value = "/import/template", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    default void getImportTemplate(ImportExcelTemplateParam param, HttpServletResponse response) {
        try {
            String fileName = param.getFileName();
            // 判断文件名是否添加时间戳
            if (Boolean.TRUE.equals(param.getTimestampSuffix())) {
                fileName = StrUtil.format("{}_{}", fileName, System.currentTimeMillis());
            }

            // 获取导入实体类
            Class<ImportBean> importClass = getImportExcelClass();
            if (importClass == null) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "无法获取导入模板");
                return;
            }

            // 设置响应头
            response.setContentType(FileMagicNumber.XLSX.getMimeType());
            response.setCharacterEncoding("utf-8");
            // 将文件名中的特殊字符转换为URL安全的格式
            String filename = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
            String contentDisposition = StrUtil.format("attachment;filename*=utf-8''{}.xlsx", filename);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);

            // 创建ExcelWriterBuilder
            ExcelWriterSheetBuilder sheet = FastExcel.write(response.getOutputStream(), importClass).sheet();
            sheet.sheetName(param.getSheetName());

            // 注册写处理器 https://fast-excel.github.io/fastexcel/zh-cn/docs/write/extra#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8
            List<WriteHandler> writeHandlers = new ArrayList<>();
            // 如果冻结表头，则注册冻结表头处理器
            if (Boolean.TRUE.equals(param.getFreezeHeader())) { writeHandlers.add(new FreezePaneHandler()); }
            // 注册-样式处理器
            writeHandlers.add(new HorizontalCellStyleStrategy(
                    ExcelStyleHandler.getHeadStyle(), ExcelStyleHandler.getContentStyle()));
            // 注册-自定义写处理器
            registerImportWriteHandlers(writeHandlers);
            writeHandlers.forEach(sheet::registerWriteHandler);

            // 写入数据
            sheet.doWrite(getImportTemplateData());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>获取导入模板基础数据</p>
     *
     * 说明：
     * <pre>
     * 1.前端下载导入模板，如果需要导入模板里面有一些基础数据，可以重写本方法
     * 2.本方法默认在导入模板里面添加一条空的数据行
     * </pre>
     *
     * @return {@code List<ImportBean>} 模板基础数据
     */
    default List<ImportBean> getImportTemplateData() {
        List<ImportBean> result = new ArrayList<>();
        try {
            // 反射创建一个ImportBean对象
            ImportBean entity = getImportExcelClass().getDeclaredConstructor().newInstance();
            result.add(entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * <p>注册导出Excel模板 自定义写处理器</p>
     *
     * 说明：<br>
     * 如果你的excel模板里面需要有一些额外信息，请重写本方法
     * <p>
     * 参考：<br>
     * <a href="https://fast-excel.github.io/fastexcel/zh-cn/docs/write/extra#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8">自定义拦截器</a>
     *
     * @param handlers 写处理器列表
     */
    default void registerImportWriteHandlers(List<WriteHandler> handlers) {}

    /**
     * 导入Excel
     *
     * @param file   导入的excel文件
     * @return ApiResult<Boolean>
     */
    @PreCheckPermission(value = {"{}:import", "{}:save"}, mode = PreMode.OR)
    @Operation(summary = "导入Excel")
    @SysLog
    @PostMapping(value = "/import", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    default ApiResult<Boolean> importExcel(@Parameter(description = "file") final MultipartFile file) throws IOException {
        // 判断文件是否为空
        if (file.isEmpty()) {
            return fail("请选择一个文件上传！");
        }

        // 判断文件类型是否是excel文件
        String typeName = "";
        try {
            typeName = FileTypeUtil.getType(file.getInputStream(), file.getOriginalFilename());
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!FileMagicNumber.XLSX.getExtension().equals(typeName)) {
            return fail("不允许的文件类型");
        }

        // 创建ExcelReaderBuilder
        ExcelReaderBuilder readerBuilder = FastExcel.read(file.getInputStream());
        // 设置表头
        readerBuilder.head(getImportExcelClass());
        // 注册读监听器
        readerBuilder.registerReadListener(getReadListener());
        // 读取数据
        readerBuilder.sheet().doRead();

        return success("文件上传并处理成功", true);
    }

    /**
     * <p>注册导入数据监听器</p>
     *
     * 说明：
     * <pre>
     * 1.你需要手动实现导入监听器，来处理导入的数据
     * 2.不要将Listener交给Spring管理。
     * </pre>
     * 相关文档：
     * <a href="https://fast-excel.github.io/fastexcel/zh-cn/docs/help/faq#%E5%B9%B6%E5%8F%91%E8%AF%BB%E5%8F%96">为何不能将Listener交给Spring管理？</a>
     */
    ReadListener<ImportBean> getReadListener();

}
